﻿using System;
using System.Diagnostics;
using System.Threading.Tasks;
using eslib.nnp5;
using eslib.nnp5.layers;

namespace eslib.nnp5
{
    /// <summary>
    /// nnp5用户层(应用层)抽象.
    /// 连接断开事件自动执行连接器的Close方法
    /// </summary>    
    public abstract class AppUserBase : IAppBase
    {
        private bool UseCRC;

        /// <summary>
        /// 传输层
        /// </summary>
        public TransportLayer transport { get; private set; } = null;


        /// <summary>
        /// 是否开启自动回复ack(收到数据包时),默认为true
        /// </summary>
        public bool EnableAutoReplyAck
        {
            get
            {
                return transport.EnableAutoReplyAck;
            }
            set
            {
                transport.EnableAutoReplyAck = value;
            }
        }


        /// <summary>
        /// AppUser绑定的对象(可选使用)
        /// </summary>
        public Object BindingObject { get; set; } = null;


        /// <summary>
        /// 发送模式
        /// </summary>
        public SenderMode senderMode { get; private set; }


        #region 队列发送

        /// <summary>
        /// 重置,当开启超时事件时，超时后会自动调用Reset
        /// </summary>
        public void Reset()
        {
            if (transport != null)
                transport.Reset();

            pkgQueue = new PackageQueue();
            AllowSend = true;
        }

        /// <summary>
        /// [线程同步的]允许发送标记，为true时可发送
        /// </summary>
        bool AllowSend
        {
            get
            {
                lock (this)
                {
                    return allowsend;
                }
            }
            set
            {
                lock (this)
                {
                    allowsend = value;
                }
            }
        }
        bool allowsend;


        /// <summary>
        /// 连接标记，断开后返回false
        /// </summary>
        public bool Connected
        {
            get
            {
                lock (this)
                {
                    return connected;
                }
            }
            set
            {
                lock (this)
                {
                    connected = value;
                }
            }
        }
        bool connected;



        /// <summary>
        /// 数据包队列（队列式发送时使用）
        /// </summary>
        PackageQueue pkgQueue;

        /// <summary>
        /// 数据队列中是否包含数据包
        /// </summary>
        internal bool QueueHasPkg => pkgQueue.HasPkg();


        /// <summary>
        /// 发送下一个包（不允许时自动取消)
        /// </summary>
        void sendNextPkg()
        {
            if (!AllowSend) return;

            //检查队列
            if (pkgQueue.HasPkg())
            {
                try
                {
                    var deq = pkgQueue.Dequeue();
                    if (deq.success)
                        send(deq.pkg);
                }
                catch { }
            }
        }



        /// <summary>
        /// 内部发送
        /// </summary>
        /// <param name="pkg"></param>        
        void send(IPackage pkg)
        {
            //收到ack前阻止下一个包发送           
            AllowSend = false;

            transport.sendData(pkg.GetBuffer(), UseCRC);

            //如果开启超时事件，启动计时
            if (EnableSendTimoutEvent)
            {
                startTimeoutTimer(pkg);
            }
        }


        #endregion


        /// <summary>
        /// 构造
        /// </summary>
        /// <param name="connector">连接器</param>
        /// <param name="useCRC">是否使用CRC</param>
        /// <param name="mode">发送器模式,默认为需要ack</param>
        public AppUserBase(Connector connector, bool useCRC = false, SenderMode mode = SenderMode.AckMode)
        {
            Reset();   //重置队列
            Connected = true;       //已连接
            senderMode = mode;      //发送模式

            this.UseCRC = useCRC;
            this.transport = new TransportLayer(connector, mode);

            connector.DisconnectEvent += Connector_DisconnectEvent;     //断开时自动释放连接器
            transport.NewACKEvent += ackEvent;
            transport.NewDataPKGEvent += Transport_NewDataPKGEvent;
        }


        #region 发送

        void ackEvent(ACKPkg ack)
        {
            //发送下一个包            
            AllowSend = true;
            sendNextPkg();


            //异步调用事件
            Task.Run(() =>
            {
                NewACKEvent(ack);
            });
        }


        /// <summary>
        /// 发送数据包到发送队列中
        /// </summary>
        /// <param name="pkg">包</param>
        public void SendPkg(IPackage pkg)
        {
            if (!Connected)     //未连接时不再允许发送
            {
                Trace.WriteLine("网络连接已断开,不允许发送数据包");
                return;
            }

            switch (senderMode)
            {
                case SenderMode.AckMode:        //ack模式
                    pkgQueue.Enqueue(pkg);
                    sendNextPkg();
                    break;

                case SenderMode.NoAckMode:      //不需要ack模式，直接发送
                    transport.sendData(pkg.GetBuffer(), UseCRC);
                    break;
            }
        }







        #region 超时事件部分

        /// <summary>
        /// 最后一次发送的数据包
        /// </summary>
        IPackage lastPkg = null;


        /// <summary>
        /// 开启超时计时器
        /// </summary>
        /// <param name="pkg"></param>
        private void startTimeoutTimer(IPackage pkg)
        {
            lastPkg = pkg;      //记录数据包

            Task.Run(async () =>
            {
                await Task.Delay(SendTimoutMS);
                if (!AllowSend)     //仍未收到ack
                {
                    Reset();        //重置

                    if (EnableAutoReSendAtTimeout)    //开启自动重发
                    {
                        send(lastPkg);
                    }
                    //事件通知
                    SendTimeoutEvent(lastPkg);
                }
            });
        }


        /// <summary>
        /// 发送超时时间(ms)，默认1500ms
        /// 启用EnableSendTimoutEvent时有效
        /// </summary>
        public int SendTimoutMS { get; set; } = 1500;

        /// <summary>
        /// [ack模式有效]
        /// 设置/获取是否使用超时事件(默认为false,不引发)
        /// 设置为true时超时引发SendTimeoutEvent(可重载)
        /// 不会自动重发数据包（由应用层自定义是否重发）
        /// </summary>
        public bool EnableSendTimoutEvent { get; set; } = false;

        /// <summary>
        /// [ack模式有效]
        /// [开启发送超时事件时有]开启时，超时自动重发数据包
        /// </summary>
        public bool EnableAutoReSendAtTimeout { get; set; } = false;


        /// <summary>
        /// 发送超时时引发(开启超时设置生效)
        /// </summary>
        /// <param name="pkg"></param>
        public virtual void SendTimeoutEvent(IPackage pkg) { }

        #endregion


        #endregion


        /// <summary>
        /// 断开连接器(connector.close())
        /// </summary>
        public void Close()
        {
            //引发断开连接事件
            Connector_DisconnectEvent(transport.connector, "主动Close");
        }




        /// <summary>
        /// 部断开连接事件
        /// </summary>
        /// <param name="connector"></param>
        /// <param name="message"></param>
        private void Connector_DisconnectEvent(Connector connector, string message)
        {
            //连接断开
            Connected = false;
            Reset();       //重置队列

            //释放连接器
            try
            {
                transport.connector.close();
            }
            catch { }

            //通知上层
            Task.Run(() =>
            {
                DisconnectEvent(connector, message);
            });
        }



        /// <summary>
        /// [异步事件]连接断开处理
        /// </summary>
        /// <param name="connector"></param>
        /// <param name="message"></param>
        public abstract void DisconnectEvent(Connector connector, string message);


        /// <summary>
        /// 新数据包
        /// </summary>
        /// <param name="pkg"></param>
        void Transport_NewDataPKGEvent(TransferredPKG pkg)
        {
            byte[] obuffer = pkg.GetData();        //原始数据

            //异步调用事件
            Task.Run(() =>
            {
                NewPKGEvent(obuffer);
            });
        }

        /// <summary>
        /// 新数据包处理（异步事件)
        /// </summary>
        /// <param name="oriBuffer">原始数据</param>
        public abstract void NewPKGEvent(byte[] oriBuffer);

        /// <summary>
        /// 收到ack处理(异步事件)
        /// </summary>
        /// <param name="ack"></param>
        public abstract void NewACKEvent(ACKPkg ack);


    }
}
